﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using System.Web.Script.Serialization;
using System.Collections;
using NConf.Interfaces;
using System.Reflection;

namespace NConf
{
    public class ConfigKey : DynamicObject, IDictionary<String, Object>
    {
        //---------------------------------------------------------------------

        #region Properties

        /// <summary>
        /// A property that's really just a call to ConfigManager.GetValue.
        /// </summary>
        public dynamic Value
        {
            get
            {
                return source.GetValue(configPath, null);
            } // end get
        } // end Value

        #endregion

        //---------------------------------------------------------------------

        #region Events

        /// <summary>
        /// This event is fired whenever a value in this ConfigKey changes.
        /// </summary>
        public event EventHandler Updated = delegate { };

        /// <summary>
        /// Reference to our ConfigSource.
        /// </summary>
        public IConfigSource source { get; set; }

        #endregion

        //---------------------------------------------------------------------

        #region Internal Members

        // The string representing the path to this config key
        private string configPath;

        #endregion

        //---------------------------------------------------------------------

        #region Constructors

        /// <summary>
        /// Constructor for ConfigKey.
        /// </summary>
        /// <param name="path">The config path to this config key.</param>
        /// <param name="manager">The config manager for this config key.</param>
        public ConfigKey(string path, IConfigSource manager)
        {
            configPath = path;
            source = manager;
        } // end ConfigKey

        #endregion

        //---------------------------------------------------------------------

        #region Event Handler

        /// <summary>
        /// Event handler called when the config source updates a property.
        /// </summary>
        /// <remarks>
        /// This handler checks to see if the value updated is related to this ConfigKey, and if so, it fires it's
        /// Updated event.
        /// </remarks>
        /// <param name="sender">The object who sent the event.</param>
        /// <param name="args">The arguments the event was fired with.</param>
        public void HandleUpdate(object sender, EventArgs args)
        {
        } // end HandleUpdate

        #endregion

        //---------------------------------------------------------------------

        #region Dynamic Access

        /// <summary>
        /// Attempts to get the requested member as a config key from the config manager. If the key does not exist, we first
        /// try accessing the member as a member of the Dictionary class.
        /// </summary>
        /// <param name="binder">The GetMemberBinder containing information about the member we're attempting to access.</param>
        /// <param name="result">The results of calling/accessing the member.</param>
        /// <returns>Returns true if we successfully called or accessed the member, false otherwise.</returns>
        public override bool TryGetMember(GetMemberBinder binder, out Object result)
        {
            var member = binder.Name;
            try
            {
                if (Value.ContainsKey(member))
                {
                    // Return the
                    result = new ConfigKey(String.Join(".", configPath, member), source);
                    return true;
                }
                else
                {
                    // Attempt to access internal members
                    result = Value.GetType().GetProperty(binder.Name).GetValue(Value, null);
                    return true;
                } // end if
            }
            catch
            {
                // We create a new, empty config key, so that GetValue will work as expected.
                result = new ConfigKey(String.Join(".", configPath, member), source);
                return true;
            } // end try/catch
        } // end TryGetMember

        /// <summary>
        /// Attempts to set the member on the config key. If this fails, do nothing.
        /// </summary>
        /// <param name="binder">The GetMemberBinder containing information about the member we're attempting to set.</param>
        /// <param name="value">The value we're trying to assign to the member.</param>
        /// <returns>Returns true if successfully set, otherwise false.</returns>
        public override bool TrySetMember(SetMemberBinder binder, Object value)
        {
            var member = binder.Name;
            var path = String.Join(".", configPath, member);
            try
            {
                // Set the member on our config source.
                source.SetValue(path, value);
                return true;
            }
            catch
            {
                // Something failed.
                return false;
            } // end try/catch
        } // end TrySetMember

        /// <summary>
        /// Attempts to invoke the given method on the wrapped config object.
        /// </summary>
        /// <param name="binder">The method we're attempting to invoke.</param>
        /// <param name="args">The arguments to invoke the method with.</param>
        /// <param name="result">The results of the method invocation.</param>
        /// <returns>True if we successfully invoked the method, false otherwise.</returns>
        public override bool TryInvokeMember(InvokeMemberBinder binder, Object[] args, out Object result)
        {
            try
            {
                result = Value.GetType().InvokeMember(binder.Name, BindingFlags.InvokeMethod, null, Value, args);
                return true;
            }
            catch
            {
                result = null;
                return false;
            } // end try/catch
        } // end TryInvokeMember

        #endregion

        //---------------------------------------------------------------------

        #region String Rendering

        public string renderJson()
        {
            var serializer = new JavaScriptSerializer();
            return serializer.Serialize(Value);
        } // end renderJson

        /// <summary>
        /// Renders the ConfigKey's value as a string. Returns empty string if it has no value.
        /// </summary>
        /// <returns>A string, representing the ConfigKey's value.</returns>
        public override string ToString()
        {
            return Value.ToString();
        } // end ToString

        #endregion

        //---------------------------------------------------------------------

        #region Conversions

        /// <summary>
        /// Renders the ConfigKey's value as a string. Returns null if it has no value.
        /// </summary>
        /// <returns>A string, representing the ConfigKey's value.</returns>
        public static implicit operator string(ConfigKey obj)
        {
            return Convert.ToString(obj.Value);
        } // end implicit operator string

        /// <summary>
        /// Renders the ConfigKey's value as a int. Returns null if it has no value.
        /// </summary>
        /// <returns>A int, representing the ConfigKey's value.</returns>
        public static implicit operator int(ConfigKey obj)
        {
            return Convert.ToInt32(obj.Value);
        } // end implicit operator int

        /// <summary>
        /// Renders the ConfigKey's value as a bool. Returns null if it has no value.
        /// </summary>
        /// <returns>A bool, representing the ConfigKey's value.</returns>
        public static implicit operator bool(ConfigKey obj)
        {
            return Convert.ToBoolean(obj.Value);
        } // end implicit operator bool

        /// <summary>
        /// Renders the ConfigKey's value as a float. Returns null if it has no value.
        /// </summary>
        /// <returns>A float, representing the ConfigKey's value.</returns>
        public static implicit operator float(ConfigKey obj)
        {
            return Convert.ToSingle(obj.Value);
        } // end implicit operator float

        /// <summary>
        /// Renders the ConfigKey's value as a double. Returns null if it has no value.
        /// </summary>
        /// <returns>A double, representing the ConfigKey's value.</returns>
        public static implicit operator double(ConfigKey obj)
        {
            return Convert.ToDouble(obj.Value);
        } // end implicit operator double

        /// <summary>
        /// Renders the ConfigKey's value as a list. Returns null if it has no value.
        /// </summary>
        /// <returns>A list, representing the ConfigKey's value.</returns>
        public static implicit operator List<object>(ConfigKey obj)
        {
            return obj.Value as List<object>;
        } // end implicit operator list

        /// <summary>
        /// Renders the ConfigKey's value as a dictionary. Returns null if it has no value.
        /// </summary>
        /// <returns>A dictionary, representing the ConfigKey's value.</returns>
        public static implicit operator Dictionary<string, object>(ConfigKey obj)
        {
            return obj.Value as Dictionary<string, object>;
        } // end implicit operator dictionary

        # endregion

        //---------------------------------------------------------------------

        #region Extensions

        /// <summary>
        /// Returns whether or not this ConfigKey has a value.
        /// </summary>
        /// <remarks>
        /// A ConfigKey has a value if it's a leaf in the config hierarchy tree. If it, instead, represents a group of
        /// configuration items (i.e. a 'config section') then it has no value.
        /// </remarks>
        /// <returns>Returns true if this ConfigKey has a value, false otherwise.</returns>
        public bool HasValue()
        {
            return source.HasValue(configPath);
        } // end HasValue

        /// <summary>
        /// Attempts to get the value of the ConfigKey, otherwise returns the default value.
        /// </summary>
        /// <param name="defaultValue">The default value to return if this ConfigKey has no value.</param>
        /// <returns>An object representing the ConfigKey's value, or the default value.</returns>
        public dynamic GetValue(dynamic defaultValue=null)
        {
            if (this.HasValue())
            {
                // Return our proxy object's value
                return Value;
            }
            else
            {
                // Return the default value
                return defaultValue;
            } // end if
        } // end GetValue

        /// <summary>
        /// Checks to see if this config key contains other values (i.e. is a config section.)
        /// </summary>
        /// <returns>Returns true if this key has sub elements, false otherwise.</returns>
        public bool IsSection()
        {
            return source.IsSection(configPath);
        } // end IsSection

        #endregion

        //---------------------------------------------------------------------

        #region IDictionary Members

        public int Count
        {
            get
            {
                // Get Value as a dictionary
                var proxyDict = Value as IDictionary<string, object>;


                // If it worked, return count
                if (proxyDict != null)
                {
                    return proxyDict.Count;
                }
                else
                {
                    // Otherwise, we have no objects.
                    return 0;
                } // end if
            } // end get
        } // end Count

        public bool IsReadOnly
        {
            get
            {
                // Always attempts to allow sets.
                return false;
            } // end get
        } // end IsReadOnly

        public object this[String key]
        {
            get
            {
                // Return a new ConfigKey, at the requested path.
                return new ConfigKey(String.Join(".", configPath, key), source);
            } // end get

            set
            {
                // Currently not implemented.
                throw new NotImplementedException("This is on my todo list.");
            } // end set
        } // end this[]

        public ICollection<String> Keys
        {
            get
            {
                // Attempt to cast to a dictionary
                var proxyDict = Value as IDictionary<string, object>;

                if (proxyDict != null)
                {
                    return proxyDict.Keys;
                }
                else
                {
                    return null;
                } // end if
            } // end get
        } // end Keys

        public ICollection<Object> Values
        {
            get
            {
                // Attempt to cast to a dictionary
                var proxyDict = Value as IDictionary<string, object>;

                if (proxyDict != null)
                {
                    return proxyDict.Values;
                }
                else
                {
                    return null;
                } // end if
            } // end get
        } // end Keys

        public void Add(KeyValuePair<String, Object> kvp)
        {
            // Attempt to cast to a dictionary
            var proxyDict = Value as IDictionary<string, object>;

            // If the cast was successful..
            if (proxyDict != null)
            {
                proxyDict.Add(kvp);
            } // end if
        } // end Add

        public void Add(String key, Object value)
        {
            // Use what we've already got...
            this[key] = value;
        } // end Add

        public bool ContainsKey(String key)
        {
            // Attempt to cast to a dictionary
            var proxyDict = Value as IDictionary<string, object>;

            // If the cast was successful..
            if (proxyDict != null)
            {
                return proxyDict.ContainsKey(key);
            } // end if

            return false;
        } // end ContainsKey

        public bool TryGetValue(String key, out Object value)
        {
            // Attempt to cast to a dictionary
            var proxyDict = Value as IDictionary<string, object>;

            // If the cast was successful..
            if (proxyDict != null)
            {
                return proxyDict.TryGetValue(key, out value);
            } // end if

            value = null;
            return false;
        } // end TryGetValue

        #endregion

        //---------------------------------------------------------------------

        #region ICollection Members

        // Note: This is implemented in ways that will cause explosions, if these functions don't exist on Value.
        // This is to be expected.

        public void Clear()
        {
            Value.Clear();
        } // end Clear

        public bool Contains(KeyValuePair<String, Object> value)
        {
            return Value.Contains(value);
        } // end Contains

        public bool Remove(KeyValuePair<String, Object> value)
        {
            return Value.Remove(value);
        } // end Remove

        public bool Remove(String key)
        {
            return Value.Remove(key);
        } // end Remove

        public void CopyTo(KeyValuePair<String, Object>[] array, int index)
        {
            Value.CopyTo(array, index);
        } // end CopyTo

        #endregion

        //---------------------------------------------------------------------

        #region IEnumerable Members

        // Note: This is implemented in ways that will cause explosions, if these functions don't exist on Value.
        // This is to be expected.

        IEnumerator IEnumerable.GetEnumerator()
        {
            return Value.GetEnumerator();
        } // end GetEnumerator

        IEnumerator<KeyValuePair<String, Object>> IEnumerable<KeyValuePair<String, Object>>.GetEnumerator()
        {
            return Value.GetEnumerator();
        } // end GetEnumerator

        #endregion

        //---------------------------------------------------------------------
    } // end ConfigKey
} // end NConf namespace
